{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.7.5"
    },
    "pycharm": {
      "stem_cell": {
        "cell_type": "raw",
        "metadata": {
          "collapsed": false
        },
        "source": []
      }
    },
    "colab": {
      "name": "3.6_softmax-regression-scratch.ipynb",
      "provenance": []
    },
    "accelerator": "GPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "collapsed": true,
        "id": "wY6YVLaoCz82"
      },
      "source": [
        "# 3.6 softmax回归的从零开始实现\n",
        "这一节我们来动手实现softmax回归。首先导入本节实现所需的包或模块。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "_KTZno1ZCz85",
        "outputId": "0aeef999-826e-4659-9147-b71b795847e9"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "2.0.0\n"
          ]
        }
      ],
      "source": [
        "import tensorflow as tf\n",
        "import numpy as np\n",
        "print(tf.__version__)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "YvWVFoCGCz89"
      },
      "source": [
        "## 3.6.1 获取和读取数据\n",
        "我们将使用Fashion-MNIST数据集，并设置批量大小为256。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "xs-z-V3NCz89"
      },
      "outputs": [],
      "source": [
        "from tensorflow.keras.datasets import fashion_mnist\n",
        "\n",
        "batch_size=256\n",
        "(x_train, y_train), (x_test, y_test) = fashion_mnist.load_data()\n",
        "x_train = tf.cast(x_train, tf.float32) / 255 #在进行矩阵相乘时需要float型，故强制类型转换为float型\n",
        "x_test = tf.cast(x_test,tf.float32) / 255 #在进行矩阵相乘时需要float型，故强制类型转换为float型\n",
        "train_iter = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(batch_size)\n",
        "test_iter = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(batch_size)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "AM-uxevSCz9A"
      },
      "source": [
        "## 3.6.2 初始化模型参数\n",
        "跟线性回归中的例子一样，我们将使用向量表示每个样本。已知每个样本输入是高和宽均为28像素的图像。模型的输入向量的长度是 28×28=784：该向量的每个元素对应图像中每个像素。由于图像有10个类别，单层神经网络输出层的输出个数为10，因此softmax回归的权重和偏差参数分别为784×10和1×10的矩阵。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "MZdS_bxZCz9B"
      },
      "outputs": [],
      "source": [
        "num_inputs = 784\n",
        "num_outputs = 10\n",
        "W = tf.Variable(tf.random.normal(shape=(num_inputs, num_outputs), mean=0, stddev=0.01, dtype=tf.float32))\n",
        "b = tf.Variable(tf.zeros(num_outputs, dtype=tf.float32))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "wb-yKAMzCz9D"
      },
      "source": [
        "## 3.6.3. 实现softmax运算¶\n",
        "在介绍如何定义softmax回归之前，我们先描述一下对如何对多维Tensor按维度操作。在下面的例子中，给定一个Tensor矩阵X。我们可以只对其中同一列（axis=0）或同一行（axis=1）的元素求和，并在结果中保留行和列这两个维度（keepdims=True）。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 105
        },
        "colab_type": "code",
        "id": "xJpGi13bCz9D",
        "outputId": "4f8fd6f5-0652-4f85-85d0-aab0947d5877"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(<tf.Tensor: id=462401, shape=(1, 3), dtype=int32, numpy=array([[5, 7, 9]], dtype=int32)>,\n",
              " <tf.Tensor: id=462403, shape=(2, 1), dtype=int32, numpy=\n",
              " array([[ 6],\n",
              "        [15]], dtype=int32)>)"
            ]
          },
          "execution_count": 21,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "X = tf.constant([[1, 2, 3], [4, 5, 6]])\n",
        "tf.reduce_sum(X, axis=0, keepdims=True), tf.reduce_sum(X, axis=1, keepdims=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "6bF8yM6xCz9F"
      },
      "source": [
        "下面我们就可以定义前面小节里介绍的softmax运算了。在下面的函数中，矩阵X的行数是样本数，列数是输出个数。为了表达样本预测各个输出的概率，softmax运算会先通过exp函数对每个元素做指数运算，再对exp矩阵同行元素求和，最后令矩阵每行各元素与该行元素之和相除。这样一来，最终得到的矩阵每行元素和为1且非负。因此，该矩阵每行都是合法的概率分布。softmax运算的输出矩阵中的任意一行元素代表了一个样本在各个输出类别上的预测概率"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "LQrwwlYGCz9G"
      },
      "outputs": [],
      "source": [
        "def softmax(logits, axis=-1):\n",
        "    return tf.exp(logits)/tf.reduce_sum(tf.exp(logits), axis, keepdims=True)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "AyZ-egfBCz9I"
      },
      "source": [
        "可以看到，对于随机输入，我们将每个元素变成了非负数，且每一行和为1。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 122
        },
        "colab_type": "code",
        "id": "6MF4TDkXCz9J",
        "outputId": "b3ae12dc-3871-4dcd-a9fc-8e5d6a1ed7dd"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "(<tf.Tensor: id=462414, shape=(2, 5), dtype=float32, numpy=\n",
              " array([[0.07188913, 0.19016613, 0.21624805, 0.40005335, 0.12164329],\n",
              "        [0.20424965, 0.22559293, 0.13348413, 0.2243966 , 0.21227665]],\n",
              "       dtype=float32)>,\n",
              " <tf.Tensor: id=462416, shape=(2,), dtype=float32, numpy=array([1.        , 0.99999994], dtype=float32)>)"
            ]
          },
          "execution_count": 23,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "X = tf.random.normal(shape=(2, 5))\n",
        "X_prob = softmax(X)\n",
        "X_prob, tf.reduce_sum(X_prob, axis=1)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "8O2hfgKsCz9L"
      },
      "source": [
        "## 3.6.4. 定义模型¶\n",
        "有了softmax运算，我们可以定义上节描述的softmax回归模型了。这里通过reshape函数将每张原始图像改成长度为num_inputs的向量。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "o1HaLQ3VCz9L"
      },
      "outputs": [],
      "source": [
        "def net(X):\n",
        "    logits = tf.matmul(tf.reshape(X, shape=(-1, W.shape[0])), W) + b\n",
        "    return softmax(logits)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "mlt3LTadCz9N"
      },
      "source": [
        "3.6.5. 定义损失函数¶\n",
        "上一节中，我们介绍了softmax回归使用的交叉熵损失函数。为了得到标签的预测概率，我们可以使用pick函数。在下面的例子中，变量y_hat是2个样本在3个类别的预测概率，变量y是这2个样本的标签类别。通过使用pick函数，我们得到了2个样本的标签的预测概率。与“softmax回归”一节数学表述中标签类别离散值从1开始逐一递增不同，在代码中，标签类别的离散值是从0开始逐一递增的。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "nrpGe702Cz9O"
      },
      "source": [
        "下面实现了“softmax回归”一节中介绍的交叉熵损失函数。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "2AwDkGcJCz9O",
        "outputId": "1a0460bc-ffe4-4682-ec97-9a6548ff8f34"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<tf.Tensor: id=462449, shape=(2,), dtype=float64, numpy=array([0.1, 0.5])>"
            ]
          },
          "execution_count": 25,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "y_hat = np.array([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])\n",
        "y = np.array([0, 2], dtype='int32')\n",
        "tf.boolean_mask(y_hat, tf.one_hot(y, depth=3))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "zMHnt4tdCz9S"
      },
      "outputs": [],
      "source": [
        "def cross_entropy(y_hat, y):\n",
        "        y = tf.cast(tf.reshape(y, shape=[-1, 1]),dtype=tf.int32)\n",
        "        y = tf.one_hot(y, depth=y_hat.shape[-1])\n",
        "        y = tf.cast(tf.reshape(y, shape=[-1, y_hat.shape[-1]]),dtype=tf.int32)\n",
        "        return -tf.math.log(tf.boolean_mask(y_hat, y)+1e-8)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "-fNq8kI_Cz9a"
      },
      "source": [
        "## 3.6.6 计算分类准确率\n",
        "给定一个类别的预测概率分布y_hat，我们把预测概率最大的类别作为输出类别。如果它与真实类别y一致，说明这次预测是正确的。分类准确率即正确预测数量与总预测数量之比。\n",
        "\n",
        "为了演示准确率的计算，下面定义准确率accuracy函数。其中tf.argmax(dim=1)返回矩阵y_hat每行中最大元素的索引，且返回结果与变量y形状相同。相等条件判断式(y_hat.argmax(dim=1) == y)是一个类型为ByteTensor的Tensor，我们用float()将其转换为值为0（相等为假）或1（相等为真）的浮点型Tensor。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "rd4Bx78jCz9a"
      },
      "outputs": [],
      "source": [
        "def accuracy(y_hat, y):\n",
        "    return np.mean((tf.argmax(y_hat, axis=1) == y))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "WgQc6LdqCz9c"
      },
      "source": [
        "让我们继续使用在演示pick函数时定义的变量y_hat和y，并将它们分别作为预测概率分布和标签。可以看到，第一个样本预测类别为2（该行最大元素0.6在本行的索引为2），与真实标签0不一致；第二个样本预测类别为2（该行最大元素0.5在本行的索引为2），与真实标签2一致。因此，这两个样本上的分类准确率为0.5。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "PxCfS-0NCz9d",
        "outputId": "6d94d406-532a-4662-87e9-4c88a71d1d80"
      },
      "outputs": [
        {
          "data": {
            "text/plain": [
              "0.5"
            ]
          },
          "execution_count": 29,
          "metadata": {
            "tags": []
          },
          "output_type": "execute_result"
        }
      ],
      "source": [
        "accuracy(y_hat, y)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "v7mQ-Zi6Cz9e"
      },
      "source": [
        "类似地，我们可以评价模型net在数据集data_iter上的准确率"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 0,
      "metadata": {
        "colab": {},
        "colab_type": "code",
        "id": "QYp3cxWhCz9f"
      },
      "outputs": [],
      "source": [
        "# 描述,对于tensorflow2中，比较的双方必须类型都是int型，所以要将输出和标签都转为int型\n",
        "def evaluate_accuracy(data_iter, net):\n",
        "    acc_sum, n = 0.0, 0\n",
        "    for _, (X, y) in enumerate(data_iter):\n",
        "        y = tf.cast(y,dtype=tf.int64)\n",
        "        acc_sum += np.sum(tf.cast(tf.argmax(net(X), axis=1), dtype=tf.int64) == y)\n",
        "#         print(acc_sum.shape,y.shape,acc_sum.dtype,y.dtype)\n",
        "        n += y.shape[0]\n",
        "    return acc_sum / n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "colab_type": "code",
        "id": "GTSuqfbPCz9h",
        "outputId": "921d04d1-5b7f-4682-bfe9-ec8144e80f93"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "0.0834\n"
          ]
        }
      ],
      "source": [
        "print(evaluate_accuracy(test_iter, net))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "collapsed": true,
        "id": "yo2WH-rpCz9k"
      },
      "source": [
        "## 3.6.7. 训练模型¶\n",
        "训练softmax回归的实现跟“线性回归的从零开始实现”一节介绍的线性回归中的实现非常相似。我们同样使用小批量随机梯度下降来优化模型的损失函数。在训练模型时，迭代周期数num_epochs和学习率lr都是可以调的超参数。改变它们的值可能会得到分类更准确的模型。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 102
        },
        "colab_type": "code",
        "id": "z3hq8k98Cz9l",
        "outputId": "a60e5d47-4e75-4337-abb6-9f63bb390773"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "epoch 1, loss 0.8969, train acc 0.736, test acc 0.813\n",
            "epoch 2, loss 0.5987, train acc 0.806, test acc 0.826\n",
            "epoch 3, loss 0.5524, train acc 0.820, test acc 0.832\n",
            "epoch 4, loss 0.5297, train acc 0.826, test acc 0.834\n",
            "epoch 5, loss 0.5139, train acc 0.830, test acc 0.836\n"
          ]
        }
      ],
      "source": [
        "# 这里使用 1e-3 学习率，是因为原文 0.1 的学习率过大，会使 cross_entropy loss 计算返回 numpy.nan\n",
        "num_epochs, lr = 5, 1e-3\n",
        "\n",
        "# 本函数已保存在d2lzh包中方便以后使用\n",
        "def train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size,\n",
        "              params=None, lr=None, trainer=None):\n",
        "    global sample_grads\n",
        "    for epoch in range(num_epochs):\n",
        "        train_l_sum, train_acc_sum, n = 0.0, 0.0, 0\n",
        "        for X, y in train_iter:\n",
        "            with tf.GradientTape() as tape:\n",
        "                y_hat = net(X)\n",
        "                l = tf.reduce_sum(loss(y_hat, y))\n",
        "            \n",
        "            grads = tape.gradient(l, params)\n",
        "            if trainer is None:\n",
        "                \n",
        "                sample_grads = grads\n",
        "                params[0].assign_sub(grads[0] * lr)\n",
        "                params[1].assign_sub(grads[1] * lr)\n",
        "            else:\n",
        "                trainer.apply_gradients(zip(grads, params))  # “softmax回归的简洁实现”一节将用到\n",
        "\n",
        "            y = tf.cast(y, dtype=tf.float32)\n",
        "            train_l_sum += l.numpy()\n",
        "            train_acc_sum += tf.reduce_sum(tf.cast(tf.argmax(y_hat, axis=1) == tf.cast(y, dtype=tf.int64), dtype=tf.int64)).numpy()\n",
        "            n += y.shape[0]\n",
        "        test_acc = evaluate_accuracy(test_iter, net)\n",
        "        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'\n",
        "              % (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))\n",
        "\n",
        "trainer = tf.keras.optimizers.SGD(lr)\n",
        "train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, batch_size, [W, b], lr)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "A5Yd8TLYHwpK"
      },
      "source": [
        "## 3.6.8 预测"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 44,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 129
        },
        "colab_type": "code",
        "id": "hJ9GGDHAHwO3",
        "outputId": "9d272afe-f657-481d-c06e-7f6e851afa65"
      },
      "outputs": [
        {
          "data": {
            "image/png": "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\niqIoiqLkDf8fpCBpux9AAJQAAAAASUVORK5CYII=\n",
            "text/plain": [
              "<Figure size 864x864 with 9 Axes>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "X, y = iter(test_iter).next()\n",
        "\n",
        "def get_fashion_mnist_labels(labels):\n",
        "    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']\n",
        "    return [text_labels[int(i)] for i in labels]\n",
        "\n",
        "def show_fashion_mnist(images, labels):\n",
        "    # 这⾥的_表示我们忽略（不使⽤）的变量\n",
        "    _, figs = plt.subplots(1, len(images), figsize=(12, 12)) # 这里注意subplot 和subplots 的区别\n",
        "    for f, img, lbl in zip(figs, images, labels):\n",
        "        f.imshow(tf.reshape(img, shape=(28, 28)).numpy())\n",
        "        f.set_title(lbl)\n",
        "        f.axes.get_xaxis().set_visible(False)\n",
        "        f.axes.get_yaxis().set_visible(False)\n",
        "    plt.show()\n",
        "\n",
        "true_labels = get_fashion_mnist_labels(y.numpy())\n",
        "pred_labels = get_fashion_mnist_labels(tf.argmax(net(X), axis=1).numpy())\n",
        "titles = [true + '\\n' + pred for true, pred in zip(true_labels, pred_labels)]\n",
        "\n",
        "show_fashion_mnist(X[0:9], titles[0:9])"
      ]
    }
  ]
}